/*
 * SPDX-License-Identifier:	GPL-2.0+
 */
#include <common.h>
#include <watchdog.h>
#include <asm/io.h>
#include <asm/utils.h>
#include <asm/arch/hardware.h>

#define WDOG_LOAD_VALUE_OFFSET			0x00
#define WDOG_CTRL_OFFSET				0x04
#define WDOG_INT_CLEAR_OFFSET			0x08
#define WDOG_RAW_INT_OFFSET				0x0C
#define WDOG_INT_STATUS_OFFSET			0x10
#define WDOG_CNT_VALUE_OFFSET			0x14
#define WDOG_OP_LOCK_OFFSET				0x18
#define WDOG_CNT_READ_OFFSET			0x1C
#define WDOG_IRQ_VALUE_OFFSET			0x20
#define WDOG_UNLOCK_KEY					(0x1ACCE551)

/*
 * WDT_CTRL Register
 */
#define WDOG_IRQ_EN						BIT(0)
#define WDOG_RUN						BIT(1)
#define WDOG_NEW						BIT(2)
#define WDOG_RST_EN						BIT(3)
#define WDOG_WAIT_SYNC					BIT(4)
#define WDT_CLOCK						32768

#define WDT_DEFAULT_TIMEOUT		10

static int timeout = WDT_DEFAULT_TIMEOUT * WDT_CLOCK;

/*
 * Set the watchdog time interval.
 * Counter is 32 bit.
 */

static void fh_wdt_write_reg(u32 offset, u32 val, u32 mask)
{
	u32 reg = GET_REG(WDT_REG_BASE + offset);
	reg &= ~mask;
	reg |= val;

	SET_REG(WDT_REG_BASE + WDOG_OP_LOCK_OFFSET, WDOG_UNLOCK_KEY);
	SET_REG(WDT_REG_BASE + offset, val);
	SET_REG(WDT_REG_BASE + WDOG_OP_LOCK_OFFSET, 0);	
}

static int fh_wdt_settimeout(unsigned int timeout_s)
{
	timeout = timeout_s * WDT_CLOCK;
	fh_wdt_write_reg(WDOG_LOAD_VALUE_OFFSET, timeout, 0xffffffff);
	return 0;
}

static void fh_wdt_enable(void)
{
	unsigned long data;
	u32 reg;

	data = WDOG_RUN | WDOG_NEW;
	data |= WDOG_RST_EN;
	data &= ~WDOG_IRQ_EN;
	fh_wdt_write_reg(WDOG_CTRL_OFFSET, data, 0xffffffff);
	fh_wdt_write_reg(WDOG_LOAD_VALUE_OFFSET, timeout, 0xffffffff);
	fh_wdt_write_reg(WDOG_IRQ_VALUE_OFFSET, 0, 0xffffffff);
	do
	{
		reg = GET_REG(WDT_REG_BASE + WDOG_RAW_INT_OFFSET);
	}while (reg & WDOG_WAIT_SYNC);
}

static unsigned int fh_wdt_is_enabled(void)
{
	unsigned int val;

	val = GET_REG(WDT_REG_BASE + WDOG_CTRL_OFFSET);
	return (val & 0x2);
}

#if defined(CONFIG_HW_WATCHDOG)
void hw_watchdog_reset(void)
{
	if (fh_wdt_is_enabled())
		/* restart the watchdog counter */
		fh_wdt_write_reg(WDOG_LOAD_VALUE_OFFSET,
				timeout, 0xffffffff);
}

void hw_watchdog_init(void)
{
	/* enable the watchdog */
	fh_wdt_enable();
	/* reset the watchdog */
	hw_watchdog_reset();
}

void hw_watchdog_settimeout(unsigned int timeout)
{
	fh_wdt_settimeout(timeout);
}

#endif
